﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Get_http_requests;

namespace Get_http_requests
{
    //这个类是在tcp监听到连接后实例化的，并且用该类的process函数去解析接收到的数据
    class HttpProcessor
    {
        private const int BUF_SIZE = 4096;//post命令解析使用的缓存长度

        public TcpClient socket;
        public HttpServer srv;

        private Stream inputStream;
        public StreamWriter outputStream;

        public String http_method;
        public String http_url;
        public String http_protocol_versionstring;
        public Hashtable httpHeaders = new Hashtable();

        private static int MAX_POST_SIZE = 10 * 1024 * 1024; // 10MB 该值为服务器设定的一个阀值，并没有真正申请内存

        /// <summary>
        /// //构造函数
        /// </summary>
        /// <param name="s"></param>
        /// <param name="srv"></param>
        public HttpProcessor(TcpClient s, HttpServer srv)
        {
            this.socket = s;
            this.srv = srv;
        }


        private string streamReadLine(Stream inputStream)
        {
            int next_char;
            string data = "";
            while (true)
            {
                next_char = inputStream.ReadByte();
                if (next_char == '\n') { break; }
                if (next_char == '\r') { continue; }
                if (next_char == -1) { Thread.Sleep(1); continue; };
                data += Convert.ToChar(next_char);
            }
            return data;
        }


        /// <summary>
        /// 工作线程，该函数在tcp接收到请求并建立HttpProcessor对象后被作为新线程任务执行
        /// </summary>
        public void process()
        {
            // we can't use a StreamReader for input, because it buffers up extra data on us inside it's
            // "processed" view of the world, and we want the data raw after the headers
            inputStream = new BufferedStream(socket.GetStream());//输入流

            // we probably shouldn't be using a streamwriter for all output from handlers either
            outputStream = new StreamWriter(new BufferedStream(socket.GetStream()));//输出流

            try
            {
                if (parseRequest())
                {
                    //http报文首行解析成功
                    if (readHeaders())//
                    {
                        //解析header成功，开始根据请求的方法调用不同的处理函数
                        if (http_method.Equals("GET"))
                        {
                            handleGETRequest();
                        }
                        #region
                        /*
                         * 接口添加处
                         * /GW_ID                       网关ID
                         * /GW_Name                     网关名称
                         * /GW_Long_Lati_Alti           网关经度纬度海拔
                         * /GW_Periodic_Work            网关守护进程工作是否打开
                         * /GW_Periodic_Work_Interval   网关守护进程工作启动周期时间，单位为秒
                         * /GW_Auto_Delete_Data_Enbale  网关周期删除数据使能
                         * /GW_Auto_Delete_Data_Save_Time   网关数据保存时间，单位为天
                         * /GW_USER_PW                  网关更改登录密码
                         * /GW_API_Port                 网关API调用接口
                         * /GW_Data_Stream_Enable       网关上行数据流使能
                         * /GW_Local_IP                 网关本地IP，该值用于摄像头的内外网穿透使用
                         * /GW_Alarm_Enable             网关报警灯控制器使能——新的属性
                         * /GW_Alarm_Port               网关报警灯控制器连接端口号
                         * /GW_Alarm_BaudRate           网关报警灯控制器比特率
                         * /GW_Alarm_Reset              网关报警灯重置，即关闭报警灯
                         * 
                         * /GW_Parm_Set                 网关相关参数一次性设置接口
                         * 
                         * /CAMERA_IP                   摄像头地址，该端是Lite系统使用的
                         * /CAMERA_Server_Port          摄像头服务端口，该端是Lite系统使用的
                         * /CAMERA_PW                   摄像头登录密码，该端是Lite系统使用的
                         * /CAMERA_USER                 摄像头登录用户名，该端是Lite系统使用的
                         * /CAMERA_Public_IP            摄像头公网IP，不含端口号，该地址公网穿透使用
                         * /CAMERA_WEB_Port             摄像头web地址，该端口号视频浏览使用，需要同穿透端口号一致
                         * 
                         * /CAMERA_Parm_Set             摄像头相关参数一次性设置接口
                         * 
                         * /Tdlas_Name             固定式传感器名称，该值主要用以管理使用
                         * /Tdlas_RS485_Port_Name       固定式传感器485通信端口号
                         * /Tdlas_RS485_Port_BaudRate   固定式传感器485通信比特率
                         * /Tdlas_RS485_Addr            固定式传感器485通信地址
                         * /Tdlas_NO_PAN_TILT_Interval  固定式设备无云台监测间隔
                         * /Tdlas_PAN_TILT              固定式设备是否有云台
                         * /Tdlas_PAN_TILT_Preset_Points固定式设备云台预置位数量
                         * /Tdlas_PAN_TILT_Every_Preset_Points_Delay 固定式设备云台预置位之间间隔时间
                         * /Tdlas_PAN_TILT_Reset_Cycles 固定式设备云台重置运行间隔
                         * /Tdlas_PAN_TILT_Run_Mode     固定式设备云台运行模式
                         * /Tdlas_Pan_Tilt_Control      固定式设备获取云台移动控制权接口
                         * /Tdlas_Pan_Tilt_Control_Refresh    固定式设备云台被连续控制时刷新控制时间
                         * /Tdlas_Pan_Tilt_Move         固定式设备云台移动
                         * /Tdlas_Pan_Tilt_Stop         固定式设备云台移动
                         * /Tdlas_Work_State            固定式设备布防撤防设置
                         * /Tdlas_Work_State_Check      固定式设备布防撤防状态获取
                         * /Tdlas_Waring_Threshold      固定式设备报警阀值设定
                         * /Tdlas_Waring_Threshold_Check固定式设备报警阀值控制
                         * /Tdlas_Dev_Reset             固定式设备重启
                         * 
                         * /Tdlas_Parm_Set              固定式设备相关参数一次性设置接口
                         * 
                         * 当前接口还缺少获取配置参数接口，缺少web端周期获取实时数据接口
                         * 
                         */
                        #endregion
                        else if (http_method.Equals("POST") &&
                            (
                                (http_url == "/Alarm/SendEquipmentAlarmInfo")   ||
                                (http_url == "/Alarm/UploadImg")  
                                
                             )
                          )//此处增加判定url的代码
                        {
                            handlePOSTRequest(http_url);//该函数增加一个参数传入请求的url，在函数内部实现对不同url进行解析
                        }
                        else
                        {
                            Syslogs.Error("POST URL Err");
                        }
                    }
                }
                outputStream.Flush();

            }
            catch (Exception e)
            {
                Syslogs.Error("Http Post Data Parse Err " + e.ToString());
            }
            //以下代码如果web服务器关闭系统会崩溃
            //outputStream.Flush();
            // bs.Flush(); // flush any remaining output
            inputStream = null; outputStream = null; // bs = null;            
            socket.Close();
        }


        /// <summary>
        /// 解析http报文第一行数据格式是否正确，如果错误则直接返回失败
        /// </summary>
        public bool parseRequest()
        {
            String request = streamReadLine(inputStream);//读取http的第一行数据
            string[] tokens = request.Split(' ');//利用空格分隔读取到的第一行数据，根据http报文格式，应该能够分成三个字段，分别为 方法+空格+url+http协议版本+回车
            if (tokens.Length != 3)//如果不是三个字段，则代表请求报文错误
            {
                //throw new Exception("invalid http request line");
                Syslogs.Warn("GW Http Server Parser Post Err");
                return false;
            }
            try
            {
                http_method = tokens[0].ToUpper();//方法名，此处本项目应该为post，此处是将方法名全部改为大写
                http_url = tokens[1];//请求的资源
                http_protocol_versionstring = tokens[2];//http报文版本
            }
            catch (Exception e)
            {
                Syslogs.Warn("GW Http Server Parser Post Err " + e.ToString());
                return false;
            }

            return true;

        }

        /// <summary>
        /// 解析http报文header
        /// </summary>
        public bool readHeaders()
        {
            String line;
            //此处开始读取请求头，根据http报文格式，第一行为请求行，第二行开始直至空行之间为请求头，此处就是读取请求头
            while ((line = streamReadLine(inputStream)) != null)
            {
                if (line.Equals(""))
                {
                    //header数据同body数据之间有一行空行，此处是判断当读取到了空行则代表header读取完毕
                    Syslogs.Debug("GW Http Server Parser Http Post Header OK");
                    return true;
                }

                int separator = line.IndexOf(':');//此处是处理header的每一个属性，属性都是以 名称:内容 来表达的，每一个属性一行，所以此处是寻找某一行的 ： 位置，在此位置之后就是属性内容了
                if (separator == -1)//如果没有匹配到 冒号 则代表改行属性格式有错误
                {
                    Syslogs.Warn("GW Http Server Parser Http Post Header Err");
                    return false;
                }

                try
                {
                    String name = line.Substring(0, separator);//截取属性名称
                    int pos = separator + 1;
                    while ((pos < line.Length) && (line[pos] == ' '))//此处应该是定位属性名称后冒号之后到属性值之前的空格的数量，最终退出该循环之后pos的值应该就是 属性值的开始位置
                    {
                        pos++; // strip any spaces
                    }

                    string value = line.Substring(pos, line.Length - pos);
                    httpHeaders[name] = value;//将header保存至键值对缓存中
                }
                catch (Exception e)
                {
                    Syslogs.Warn(" GW Http Server Parser Http Post Header Err " + e.ToString());
                    return false;
                }

            }
            //代码不应该到达此处
            Syslogs.Warn("GW Http Server Parser Http Post Header Err");
            return true;
        }

        /// <summary>
        /// 调用http server 对象中的处理PUT请求的函数，srv为解析类在构造的时候引入的http server类的对象实例
        /// </summary>
        public void handleGETRequest()
        {
            srv.handleGETRequest(this);
        }

        /// <summary>
        /// 调用http server 对象中的处理POST请求的函数，srv为解析类在构造的时候引入的http server类的对象实例
        /// </summary>
        /// <param name="http_url">请求的资源</param>
        public void handlePOSTRequest(string http_url)
        {

            //Console.WriteLine("get post data start");
            int content_len = 0;
            MemoryStream ms = new MemoryStream();
            if (this.httpHeaders.ContainsKey("Content-Length"))//该值为header里面表示body的数据长度的值（这个表达方式是否正确不太清楚），该值如果没有就认为body包错误
            {
                content_len = Convert.ToInt32(this.httpHeaders["Content-Length"]);
                if (content_len > MAX_POST_SIZE)//如果为真，则代表传输的数据量太大
                {
                    Syslogs.Warn(" GW Http Server POST Content-Length(" + content_len + ") too big for this simple server");
                    return;
                }
                byte[] buf = new byte[BUF_SIZE];
                int to_read = content_len;
                while (to_read > 0)
                {
                    //读取指定长度的数据，此时读取的是body的数据
                    //注意Math.Min的用法，服务器整体接收body数据最长长度设定了一个阀值，为 MAX_POST_SIZE，当前请求的数据长度为content_len，如果大于MAX_POST_SIZE则程序不会到达此处
                    //但content_len小于MAX_POST_SIZE的时候，在解析body数据的时候应该一次性全部读取出来，但读取接收缓存长度可能会比较小
                    //因此Math.Min方法就是读取实际待读取的数据长度，即如果要读取的数据大于BUF_SIZE，则只读取BUF_SIZE长度数据，否则就读取to_read长度的数据
                    int numread = this.inputStream.Read(buf, 0, Math.Min(BUF_SIZE, to_read));

                    if (numread == 0)
                    {
                        if (to_read == 0)
                        {
                            //没有待读取数据
                            break;
                        }
                        else
                        {
                            Syslogs.Warn(" GW Http Server Client Disconnect When Reading Post Data");
                            return;
                        }
                    }
                    to_read -= numread;
                    ms.Write(buf, 0, numread);
                }
                ms.Seek(0, SeekOrigin.Begin);

            }
            //Console.WriteLine("get post data end");
            srv.handlePOSTRequest(this, new StreamReader(ms), http_url);
        }

        public void writeSuccess()
        {
            outputStream.WriteLine("HTTP/1.0 200 OK");
            outputStream.WriteLine("Server:1.0");
            outputStream.WriteLine("Content-Type: text/html;charset=utf-8");
            //outputStream.WriteLine("Content-Type:application/x-www-form-urlencoded; charset=UTF-8");
            //outputStream.WriteLine("Content-Length:5");
            //outputStream.WriteLine("Connection: keep-alive");
            outputStream.WriteLine("Connection: close");
            outputStream.WriteLine("");
            //outputStream.WriteLine("");
        }

        public void writeFailure()
        {
            outputStream.WriteLine("HTTP/1.0 404 File not found");
            outputStream.WriteLine("Connection: close");
            outputStream.WriteLine("");
        }
    }


    /// <summary>
    /// 监听http请求类，是一个父类，由myhttpserver 继承
    /// </summary>
    abstract class HttpServer
    {

        protected int port;
        TcpListener listener;
        bool is_active = true;
        bool is_stop = false;
        public HttpServer(int port)
        {
            this.port = port;
        }

        public void http_listener_stop()
        {
            this.is_active = false;
        }

        public bool http_listener_stop_check()
        {
            return this.is_stop;
        }

        public void listen()//启动服务函数
        {
            listener = new TcpListener(IPAddress.Any, port);
            listener.Start();
            //Syslogs.Info("Gw Http Server Start Success");//该消息提示改在了启动该函数的外部函数中
            while (is_active)//一个死循环
            {
                TcpClient s = listener.AcceptTcpClient();//阻塞式监听
                if (is_active)//如果http服务器退出已经被置位，则不再处理任何业务
                {
                    //建立httpprocessor类的一个新实例，构造函数使用监听连接获取到的数据和一个HttpProcessor对象实例
                    HttpProcessor processor = new HttpProcessor(s, this);
                    //新建一个处理线程
                    Thread thread = new Thread(new ThreadStart(processor.process));
                    //开始处理本次接收到的请求数据
                    thread.Start();
                }
                Thread.Sleep(100);
            }
            listener.Stop();
            this.is_stop = true;
            Syslogs.Info("Gw Http Server Stop Success");
        }

        //处理GET和POST的抽象函数，具体实现在继承类中
        public abstract void handleGETRequest(HttpProcessor p);
        public abstract void handlePOSTRequest(HttpProcessor p, StreamReader inputData, string http_url);

    }

    /// <summary>
    ///  main程序中构架的http 对象
    /// </summary>
    class GW_HttpServer : HttpServer
    {

        bool _http_server_start_state;//http服务器启动状态，由于当前服务器支持参数错误启动，因此当为参数错误状态启动时，http服务器不响应控制命令,该值为true时为正常状态，为false时为参数异常启动状态

        public bool Http_server_start_state { get => _http_server_start_state; set => _http_server_start_state = value; }

        /// <summary>
        /// 构造函数，初始化基类
        /// </summary>
        /// <param name="port"></param>
        public GW_HttpServer(int port) : base(port)
        {        }

        public override void handleGETRequest(HttpProcessor p)
        {
            Console.WriteLine("request: {0}", p.http_url);
            p.writeSuccess();
            p.outputStream.WriteLine("<html><body><h1>test server</h1>");
            p.outputStream.WriteLine("Current Time: " + DateTime.Now.ToString());
            p.outputStream.WriteLine("url : {0}", p.http_url);

            p.outputStream.WriteLine("<form method=post action=/form>");
            p.outputStream.WriteLine("<input type=text name=foo value=foovalue>");
            p.outputStream.WriteLine("<input type=submit name=bar value=barvalue>");
            p.outputStream.WriteLine("</form>");
        }


        /// <summary>
        /// 重写HttpServer类中的抽象函数
        /// </summary>
        /// <param name="p"></param>
        /// <param name="inputData"></param>
        /// <param name="http_url"></param>
        public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData, string http_url)
        {

            string return_str = "";//返回的字符串,  ret = 0  snesor_port=请求的值  delay = time 该点位的延时

            //将post的数据一次性全部读取出来，post的数据应该为 Sensor_Port= 端口号（端口号取值为1到4）， State=1或者0，1为获取，0为放弃
            string data = inputData.ReadToEnd();
            //增加url判定
            if (http_url == "/Alarm/SendEquipmentAlarmInfo")
            {

                return_str += "{\"code\":\"成功\",\"message\":\"null\",\"data\":\"null\"}" ;
                Console.WriteLine(return_str);
            }
            else if ((http_url == "/Alarm/UploadImg"))
            {
                return_str += "{\"code\":\"image成功\",\"message\":\"null\",\"data\":\"null\"}";
                Console.WriteLine(return_str);
            }
            else
            {
                Syslogs.Info("Http Post URL Err OR Http Server Work In Emergency State Can Answer This Command ");
                return_str += "{\"code\":\"失败\",\"message\":\"-1\",\"data\":\"null\"}";
            }
            p.writeSuccess();
            p.outputStream.WriteLine(return_str.ToString());
            p.outputStream.WriteLine("");
            p.outputStream.WriteLine("");

        }
    }

    /*
    public class TestMain
    {
        public static int Main(String[] args)
        {
            HttpServer httpServer;
            if (args.GetLength(0) > 0)
            {
                httpServer = new MyHttpServer(Convert.ToInt16(args[0]));
            }
            else
            {
                httpServer = new MyHttpServer(8080);
            }
            Thread thread = new Thread(new ThreadStart(httpServer.listen));
            thread.Start();
            return 0;
        }

    }
    */
}